1   /*
2    * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package com.sun.media.sound;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  
32  import javax.sound.sampled.AudioFormat;
33  import javax.sound.sampled.AudioSystem;
34  import javax.sound.sampled.BooleanControl;
35  import javax.sound.sampled.Control;
36  import javax.sound.sampled.DataLine;
37  import javax.sound.sampled.FloatControl;
38  import javax.sound.sampled.LineEvent;
39  import javax.sound.sampled.LineListener;
40  import javax.sound.sampled.Control.Type;
41  
42  /**
43   * General software mixing line.
44   *
45   * @author Karl Helgason
46   */
47  public abstract class SoftMixingDataLine implements DataLine {
48  
49      public static final FloatControl.Type CHORUS_SEND = new FloatControl.Type(
50              "Chorus Send") {
51      };
52  
53      protected static class AudioFloatInputStreamResampler extends
54              AudioFloatInputStream {
55  
56          private AudioFloatInputStream ais;
57  
58          private AudioFormat targetFormat;
59  
60          private float[] skipbuffer;
61  
62          private SoftAbstractResampler resampler;
63  
64          private float[] pitch = new float[1];
65  
66          private float[] ibuffer2;
67  
68          private float[][] ibuffer;
69  
70          private float ibuffer_index = 0;
71  
72          private int ibuffer_len = 0;
73  
74          private int nrofchannels = 0;
75  
76          private float[][] cbuffer;
77  
78          private int buffer_len = 512;
79  
80          private int pad;
81  
82          private int pad2;
83  
84          private float[] ix = new float[1];
85  
86          private int[] ox = new int[1];
87  
88          private float[][] mark_ibuffer = null;
89  
90          private float mark_ibuffer_index = 0;
91  
92          private int mark_ibuffer_len = 0;
93  
94          public AudioFloatInputStreamResampler(AudioFloatInputStream ais,
95                  AudioFormat format) {
96              this.ais = ais;
97              AudioFormat sourceFormat = ais.getFormat();
98              targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
99                      .getSampleRate(), sourceFormat.getSampleSizeInBits(),
100                     sourceFormat.getChannels(), sourceFormat.getFrameSize(),
101                     format.getSampleRate(), sourceFormat.isBigEndian());
102             nrofchannels = targetFormat.getChannels();
103             Object interpolation = format.getProperty("interpolation");
104             if (interpolation != null && (interpolation instanceof String)) {
105                 String resamplerType = (String) interpolation;
106                 if (resamplerType.equalsIgnoreCase("point"))
107                     this.resampler = new SoftPointResampler();
108                 if (resamplerType.equalsIgnoreCase("linear"))
109                     this.resampler = new SoftLinearResampler2();
110                 if (resamplerType.equalsIgnoreCase("linear1"))
111                     this.resampler = new SoftLinearResampler();
112                 if (resamplerType.equalsIgnoreCase("linear2"))
113                     this.resampler = new SoftLinearResampler2();
114                 if (resamplerType.equalsIgnoreCase("cubic"))
115                     this.resampler = new SoftCubicResampler();
116                 if (resamplerType.equalsIgnoreCase("lanczos"))
117                     this.resampler = new SoftLanczosResampler();
118                 if (resamplerType.equalsIgnoreCase("sinc"))
119                     this.resampler = new SoftSincResampler();
120             }
121             if (resampler == null)
122                 resampler = new SoftLinearResampler2(); // new
123             // SoftLinearResampler2();
124             pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
125             pad = resampler.getPadding();
126             pad2 = pad * 2;
127             ibuffer = new float[nrofchannels][buffer_len + pad2];
128             ibuffer2 = new float[nrofchannels * buffer_len];
129             ibuffer_index = buffer_len + pad;
130             ibuffer_len = buffer_len;
131         }
132 
133         public int available() throws IOException {
134             return 0;
135         }
136 
137         public void close() throws IOException {
138             ais.close();
139         }
140 
141         public AudioFormat getFormat() {
142             return targetFormat;
143         }
144 
145         public long getFrameLength() {
146             return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
147         }
148 
149         public void mark(int readlimit) {
150             ais.mark((int) (readlimit * pitch[0]));
151             mark_ibuffer_index = ibuffer_index;
152             mark_ibuffer_len = ibuffer_len;
153             if (mark_ibuffer == null) {
154                 mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
155             }
156             for (int c = 0; c < ibuffer.length; c++) {
157                 float[] from = ibuffer[c];
158                 float[] to = mark_ibuffer[c];
159                 for (int i = 0; i < to.length; i++) {
160                     to[i] = from[i];
161                 }
162             }
163         }
164 
165         public boolean markSupported() {
166             return ais.markSupported();
167         }
168 
169         private void readNextBuffer() throws IOException {
170 
171             if (ibuffer_len == -1)
172                 return;
173 
174             for (int c = 0; c < nrofchannels; c++) {
175                 float[] buff = ibuffer[c];
176                 int buffer_len_pad = ibuffer_len + pad2;
177                 for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
178                     buff[ix] = buff[i];
179                 }
180             }
181 
182             ibuffer_index -= (ibuffer_len);
183 
184             ibuffer_len = ais.read(ibuffer2);
185             if (ibuffer_len >= 0) {
186                 while (ibuffer_len < ibuffer2.length) {
187                     int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
188                             - ibuffer_len);
189                     if (ret == -1)
190                         break;
191                     ibuffer_len += ret;
192                 }
193                 Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
194                 ibuffer_len /= nrofchannels;
195             } else {
196                 Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
197             }
198 
199             int ibuffer2_len = ibuffer2.length;
200             for (int c = 0; c < nrofchannels; c++) {
201                 float[] buff = ibuffer[c];
202                 for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
203                     buff[ix] = ibuffer2[i];
204                 }
205             }
206 
207         }
208 
209         public int read(float[] b, int off, int len) throws IOException {
210 
211             if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
212                 cbuffer = new float[nrofchannels][len / nrofchannels];
213             }
214             if (ibuffer_len == -1)
215                 return -1;
216             if (len < 0)
217                 return 0;
218             int remain = len / nrofchannels;
219             int destPos = 0;
220             int in_end = ibuffer_len;
221             while (remain > 0) {
222                 if (ibuffer_len >= 0) {
223                     if (ibuffer_index >= (ibuffer_len + pad))
224                         readNextBuffer();
225                     in_end = ibuffer_len + pad;
226                 }
227 
228                 if (ibuffer_len < 0) {
229                     in_end = pad2;
230                     if (ibuffer_index >= in_end)
231                         break;
232                 }
233 
234                 if (ibuffer_index < 0)
235                     break;
236                 int preDestPos = destPos;
237                 for (int c = 0; c < nrofchannels; c++) {
238                     ix[0] = ibuffer_index;
239                     ox[0] = destPos;
240                     float[] buff = ibuffer[c];
241                     resampler.interpolate(buff, ix, in_end, pitch, 0,
242                             cbuffer[c], ox, len / nrofchannels);
243                 }
244                 ibuffer_index = ix[0];
245                 destPos = ox[0];
246                 remain -= destPos - preDestPos;
247             }
248             for (int c = 0; c < nrofchannels; c++) {
249                 int ix = 0;
250                 float[] buff = cbuffer[c];
251                 for (int i = c; i < b.length; i += nrofchannels) {
252                     b[i] = buff[ix++];
253                 }
254             }
255             return len - remain * nrofchannels;
256         }
257 
258         public void reset() throws IOException {
259             ais.reset();
260             if (mark_ibuffer == null)
261                 return;
262             ibuffer_index = mark_ibuffer_index;
263             ibuffer_len = mark_ibuffer_len;
264             for (int c = 0; c < ibuffer.length; c++) {
265                 float[] from = mark_ibuffer[c];
266                 float[] to = ibuffer[c];
267                 for (int i = 0; i < to.length; i++) {
268                     to[i] = from[i];
269                 }
270             }
271 
272         }
273 
274         public long skip(long len) throws IOException {
275             if (len > 0)
276                 return 0;
277             if (skipbuffer == null)
278                 skipbuffer = new float[1024 * targetFormat.getFrameSize()];
279             float[] l_skipbuffer = skipbuffer;
280             long remain = len;
281             while (remain > 0) {
282                 int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
283                         skipbuffer.length));
284                 if (ret < 0) {
285                     if (remain == len)
286                         return ret;
287                     break;
288                 }
289                 remain -= ret;
290             }
291             return len - remain;
292 
293         }
294 
295     }
296 
297     private class Gain extends FloatControl {
298 
299         private Gain() {
300 
301             super(FloatControl.Type.MASTER_GAIN, -80f, 6.0206f, 80f / 128.0f,
302                     -1, 0.0f, "dB", "Minimum", "", "Maximum");
303         }
304 
305         public void setValue(float newValue) {
306             super.setValue(newValue);
307             calcVolume();
308         }
309     }
310 
311     private class Mute extends BooleanControl {
312 
313         private Mute() {
314             super(BooleanControl.Type.MUTE, false, "True", "False");
315         }
316 
317         public void setValue(boolean newValue) {
318             super.setValue(newValue);
319             calcVolume();
320         }
321     }
322 
323     private class ApplyReverb extends BooleanControl {
324 
325         private ApplyReverb() {
326             super(BooleanControl.Type.APPLY_REVERB, false, "True", "False");
327         }
328 
329         public void setValue(boolean newValue) {
330             super.setValue(newValue);
331             calcVolume();
332         }
333 
334     }
335 
336     private class Balance extends FloatControl {
337 
338         private Balance() {
339             super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1,
340                     0.0f, "", "Left", "Center", "Right");
341         }
342 
343         public void setValue(float newValue) {
344             super.setValue(newValue);
345             calcVolume();
346         }
347 
348     }
349 
350     private class Pan extends FloatControl {
351 
352         private Pan() {
353             super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1,
354                     0.0f, "", "Left", "Center", "Right");
355         }
356 
357         public void setValue(float newValue) {
358             super.setValue(newValue);
359             balance_control.setValue(newValue);
360         }
361 
362         public float getValue() {
363             return balance_control.getValue();
364         }
365 
366     }
367 
368     private class ReverbSend extends FloatControl {
369 
370         private ReverbSend() {
371             super(FloatControl.Type.REVERB_SEND, -80f, 6.0206f, 80f / 128.0f,
372                     -1, -80f, "dB", "Minimum", "", "Maximum");
373         }
374 
375         public void setValue(float newValue) {
376             super.setValue(newValue);
377             balance_control.setValue(newValue);
378         }
379 
380     }
381 
382     private class ChorusSend extends FloatControl {
383 
384         private ChorusSend() {
385             super(CHORUS_SEND, -80f, 6.0206f, 80f / 128.0f, -1, -80f, "dB",
386                     "Minimum", "", "Maximum");
387         }
388 
389         public void setValue(float newValue) {
390             super.setValue(newValue);
391             balance_control.setValue(newValue);
392         }
393 
394     }
395 
396     private Gain gain_control = new Gain();
397 
398     private Mute mute_control = new Mute();
399 
400     private Balance balance_control = new Balance();
401 
402     private Pan pan_control = new Pan();
403 
404     private ReverbSend reverbsend_control = new ReverbSend();
405 
406     private ChorusSend chorussend_control = new ChorusSend();
407 
408     private ApplyReverb apply_reverb = new ApplyReverb();
409 
410     private Control[] controls;
411 
412     protected float leftgain = 1;
413 
414     protected float rightgain = 1;
415 
416     protected float eff1gain = 0;
417 
418     protected float eff2gain = 0;
419 
420     protected List<LineListener> listeners = new ArrayList<LineListener>();
421 
422     protected Object control_mutex;
423 
424     protected SoftMixingMixer mixer;
425 
426     protected DataLine.Info info;
427 
428     protected abstract void processControlLogic();
429 
430     protected abstract void processAudioLogic(SoftAudioBuffer[] buffers);
431 
432     protected SoftMixingDataLine(SoftMixingMixer mixer, DataLine.Info info) {
433         this.mixer = mixer;
434         this.info = info;
435         this.control_mutex = mixer.control_mutex;
436 
437         controls = new Control[] { gain_control, mute_control, balance_control,
438                 pan_control, reverbsend_control, chorussend_control,
439                 apply_reverb };
440         calcVolume();
441     }
442 
443     protected void calcVolume() {
444         synchronized (control_mutex) {
445             double gain = Math.pow(10.0, gain_control.getValue() / 20.0);
446             if (mute_control.getValue())
447                 gain = 0;
448             leftgain = (float) gain;
449             rightgain = (float) gain;
450             if (mixer.getFormat().getChannels() > 1) {
451                 // -1 = Left, 0 Center, 1 = Right
452                 double balance = balance_control.getValue();
453                 if (balance > 0)
454                     leftgain *= (1 - balance);
455                 else
456                     rightgain *= (1 + balance);
457 
458             }
459         }
460 
461         eff1gain = (float) Math.pow(10.0, reverbsend_control.getValue() / 20.0);
462         eff2gain = (float) Math.pow(10.0, chorussend_control.getValue() / 20.0);
463 
464         if (!apply_reverb.getValue()) {
465             eff1gain = 0;
466         }
467     }
468 
469     protected void sendEvent(LineEvent event) {
470         if (listeners.size() == 0)
471             return;
472         LineListener[] listener_array = listeners
473                 .toArray(new LineListener[listeners.size()]);
474         for (LineListener listener : listener_array) {
475             listener.update(event);
476         }
477     }
478 
479     public void addLineListener(LineListener listener) {
480         synchronized (control_mutex) {
481             listeners.add(listener);
482         }
483     }
484 
485     public void removeLineListener(LineListener listener) {
486         synchronized (control_mutex) {
487             listeners.add(listener);
488         }
489     }
490 
491     public javax.sound.sampled.Line.Info getLineInfo() {
492         return info;
493     }
494 
495     public Control getControl(Type control) {
496         if (control != null) {
497             for (int i = 0; i < controls.length; i++) {
498                 if (controls[i].getType() == control) {
499                     return controls[i];
500                 }
501             }
502         }
503         throw new IllegalArgumentException("Unsupported control type : "
504                 + control);
505     }
506 
507     public Control[] getControls() {
508         return Arrays.copyOf(controls, controls.length);
509     }
510 
511     public boolean isControlSupported(Type control) {
512         if (control != null) {
513             for (int i = 0; i < controls.length; i++) {
514                 if (controls[i].getType() == control) {
515                     return true;
516                 }
517             }
518         }
519         return false;
520     }
521 
522 }